package cz.cuni.mff.ufal.volk;

import java.io.Closeable;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Scanner;

import cz.cuni.mff.ufal.httpserver.HttpServer;
import cz.cuni.mff.ufal.volk.boot.BootCodeLoader;
import cz.cuni.mff.ufal.volk.boot.DefaultBootCodeLoader;
import cz.cuni.mff.ufal.volk.events.NabaztagEventListener;

public class NabaztagControllerImpl implements NabaztagController, Runnable, Closeable {

  public NabaztagControllerImpl() {
  	bootCodeLoader = new DefaultBootCodeLoader();
  }

  @Override
  public Collection<NabaztagProxy> getRegisteredBunnies() {
  	return handler.getRegisteredBunnies();
  }

  public void run() {
  	try {
	    this.open();
	    new Scanner(System.in).nextLine();
    } catch (IOException e) {
	    log.fatal("The attempt to start the server failed", e);
    }
  }

  public NabaztagControllerImpl(Collection<NabaztagEventListener> listeners) {
  	this();
  	if (listeners != null) // not a big problem
  		this.listeners.addAll(listeners);
  }

  //[start] settings

  /** Listener address and port, if another than default (80) should be used. */
  private String listenerAddress;
  private int listenerPort;

  /**
   * @return the listenerAddress
   */
  public String getListenerAddress() {
  	return listenerAddress;
  }

	/**
   * @param listenerAddress the listenerAddress to set
	 * @throws MalformedURLException
   */
  public void setListenerAddress(String listenerAddress) throws MalformedURLException {
  	throwIfRunning();
  	URL locator = new URL(listenerAddress);
  	this.listenerAddress = listenerAddress;
  	this.listenerPort = locator.getPort();
  }

	/**
   * @return the maxQueueSize
   */
  public int getMaxQueueSize() {
  	return maxQueueSize;
  }

	/**
   * @param maxQueueSize the maxQueueSize to set
   */
  public void setMaxQueueSize(int maxQueueSize) {
  	throwIfRunning();
  	this.maxQueueSize = maxQueueSize;
  }

	/**
   * @return the suppressMultipleEvents
   */
  public boolean isSuppressMultipleEvents() {
  	return suppressMultipleEvents;
  }

	/**
   * @param suppressMultipleEvents the suppressMultipleEvents to set
   */
  public void setSuppressMultipleEvents(boolean suppressMultipleEvents) {
  	throwIfRunning();
  	this.suppressMultipleEvents = suppressMultipleEvents;
  }

	/**
   * @return the threadPoolSize
   */
  public int getThreadPoolSize() {
  	return threadPoolSize;
  }

	/**
   * @param threadPoolSize the threadPoolSize to set
   */
  public void setThreadPoolSize(int threadPoolSize) {
  	throwIfRunning();
  	this.threadPoolSize = threadPoolSize;
  }

	/**
   * @return the workingFolder
   */
  public String getWorkingFolder() {
  	return workingFolder;
  }

	/**
   * @param workingFolder the workingFolder to set
   */
  public void setWorkingFolder(String workingFolder) {
  	throwIfRunning();
  	log.trace("call setWorkingFolder: " + workingFolder);
  	this.workingFolder = workingFolder;
  }

	/**
   * @return the maxQueueExceededBehavior
   */
  public MaxQueueExceededBehavior getMaxQueueExceededBehavior() {
  	return maxQueueExceededBehavior;
  }

	/**
   * @param maxQueueExceededBehavior the maxQueueExceededBehavior to set
   */
  public void setMaxQueueExceededBehavior(
      MaxQueueExceededBehavior maxQueueExceededBehavior) {
  	throwIfRunning();
  	this.maxQueueExceededBehavior = maxQueueExceededBehavior;
  }

  private BootCodeLoader bootCodeLoader;

  public BootCodeLoader getBootCodeLoader() {
  	return bootCodeLoader;
  }

  public void setBootCodeLoader(BootCodeLoader bootCodeLoader) {
  	throwIfRunning();
  	this.bootCodeLoader = bootCodeLoader;
  }

  public boolean getRemoveTempFiles() {
  	return removeTempFiles;
  }

  public void setRemoveTempFiles(boolean val) {
  	throwIfRunning();
  	removeTempFiles = val;
  }

	/** Maximum queue size. */
  private int maxQueueSize = 10;
  /** Indication whether only one event should be generated for each recorded sound. */
  private boolean suppressMultipleEvents = false;
  /** Size of the thread pool used by the HTTP server. */
  private int threadPoolSize = 5;
  /** Path of the working folder. */
  private String workingFolder;
  /** Indication whether the temporary files should be removed. */
  private boolean removeTempFiles = true;
  /** Preferred behavior in case of a request that would exceed the queue size. */
  private MaxQueueExceededBehavior maxQueueExceededBehavior;

  private void throwIfRunning() {
  	if (state != ControllerState.STOPPED) {
  		throw new IllegalStateException("An attempt to change settings in the incorrect state was made");
  	}
  }

  //[end]

  //[start] controller state

  private ControllerState state = ControllerState.STOPPED;

  /**
   * Retrieves the current controller state.
   * @return
   */
  public ControllerState getState() {
  	return state;
  }

  //[end]

  //[start] logger
  private static final org.apache.log4j.Logger log = org.apache.log4j.Logger
      .getLogger(NabaztagControllerImpl.class);
  //[end]

  //[start] starting and stopping the server

  private HttpServer httpServer;
  private NabaztagRequestHandler handler;

  @Override
  public void open() throws IOException {
  	log.trace(">> open()");
  	if (state != ControllerState.STOPPED) {
  		throw new IllegalStateException("An attempt to start the server in the wrong state was made");
  	}

  	handler = new NabaztagRequestHandler(listeners);
  	handler.setServerAddress(listenerAddress);
  	handler.setBootCodeLoader(bootCodeLoader);
  	handler.setRemoveTempFiles(removeTempFiles);
  	if (workingFolder == null) {
  		// TODO
  	} else {
  		handler.setTempDir(workingFolder);
  	}
  	httpServer = new HttpServer(handler);
  	httpServer.setPort(listenerPort);
  	httpServer.start();

    state = ControllerState.RUNNING;
    log.trace("<< open()");
  }

  /**
   * Stops the httpServer, closes the sockets etc.
   */
  @Override
  public void close() throws IOException {
  	log.trace(">> close()");
  	if (state != ControllerState.RUNNING) {
  		throw new IllegalStateException("An attempt to stop a not running server was made");
  	}

  	log.info("close");
  	httpServer.stop();

    state = ControllerState.STOPPED;
    log.trace("<< close()");
  }

  //[end]

  private final static String[] SUPPORTED_FORMATS = new String[] { "wav/unsigned-integer",
    "wav/signed-integer", "wav/oki-adpcm", "mp3" };

  /**
   * Checks whether the specified format is supported.
   * @param  format the format to check
   * @return whether the specified format is supported.
   */
  public static boolean isSupported(String format) {
    format.getClass();
    for (String s : SUPPORTED_FORMATS) {
      if (format.equals(s)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public void addListener(NabaztagEventListener listener) {
  	synchronized(listeners) {
  		listeners.add(listener);
  	}
  }

  @Override
  public void removeListener(NabaztagEventListener listener) {
    synchronized(listeners) {
    	listeners.remove(listener);
    }
  }

  private final List<NabaztagEventListener> listeners = new ArrayList<NabaztagEventListener>();

}
